import time
import datetime
from functools import wraps
from flask_login import current_user
from flask import current_app, request
from http import HTTPStatus
from werkzeug.local import LocalProxy
from flask_restplus.utils import merge
from casbin import Enforcer
from exlib.libs.casbin_mongoengine_adapter import Adapter
from exlib.webbase.response import JsonResponse
from casbin.model import Model as CasbinModel
from app.conf import RUN_CONFIG
from app.database import redis_client, BASE_DB
from utils import current_enterprise_id

LOG_ENABLED = False
base_model_conf = CasbinModel()
base_model_conf.add_def("r", "r", "sub, obj, act, dom")
base_model_conf.add_def("p", "p", "sub, obj, act, dom")
base_model_conf.add_def("g", "g", "_, _, _")
base_model_conf.add_def("e", "e", "some(where (p.eft == allow))")
base_model_conf.add_def("m", "m",
                        'g(r.sub, "root", r.dom) || (g(r.sub, p.sub, r.dom) && r.obj == p.obj && (p.act == r.act || p.act == "*") && keyMatch(r.dom, p.dom))')


#  casbin Enforcer 在多个进程中保持同步
AUTHS = {
    'cached_auth': None,
    'version': 0
}


class Auth:
    def __init__(self):
        casbin_adapter = Adapter()
        self.e = Enforcer(base_model_conf, casbin_adapter, LOG_ENABLED)

    @staticmethod
    def get_cached_version():
        return int(redis_client.get('casbin_auth_version') or 0) or None

    @staticmethod
    def reset_cached_version(v=None):
        if v is None:
            v = int(time.time())
        redis_client.set('casbin_auth_version', v)
        return v

    @staticmethod
    def reset_auth(new_version):
        if not AUTHS.get('cached_auth'):
            AUTHS['cached_auth'] = Auth()
        AUTHS['version'] = Auth.reset_cached_version(new_version)
        AUTHS['cached_auth'].e.load_policy()

    @staticmethod
    def get_cached_enforcer():
        now_version = Auth.get_cached_version()
        if not now_version or not AUTHS.get('version') or AUTHS['version'] != now_version:
            Auth.reset_auth(now_version)
        return AUTHS['cached_auth'].e


def get_casbin_id(user):
    return getattr(user, 'email')


def perm_required(*perms):
    def wrapper(fn):
        if perms:
            content, actions = perms
            if type(actions) is str:
                actions = [actions]
            actions_to_all = [a + '_all' for a in actions if not a.endswith('_all')]
            actions.extend(actions_to_all)
        else:
            content = '.'.join((fn.__module__ + '.' + fn.__qualname__).split('.')[-1:])
            actions = [fn.__name__]
        doc = {
            'responses': {
                'require_permission': '%s:%s' % (content, ','.join(actions))
            },
        }
        fn.__apidoc__ = merge(getattr(fn, '__apidoc__', {}), doc)

        @wraps(fn)
        def decorated_view(*args, **kwargs):
            if not current_user.is_authenticated:
                return current_app.login_manager.unauthorized()
            user = get_casbin_id(current_user)
            dom = str(current_enterprise_id)
            has_perm = True in [e.enforce(user, content, action, dom) for action in actions]
            if has_perm:
                return fn(*args, **kwargs)
            return JsonResponse(data={'status': HTTPStatus.FORBIDDEN, 'message': 'operate need permission: %s, %s' %(content, actions)},
                                status=HTTPStatus.FORBIDDEN)

        return decorated_view

    return wrapper


e = LocalProxy(lambda: Auth.get_cached_enforcer())


def reset_enforcer():
    Auth.reset_cached_version()
